package com.atguigu.gmall.item.component;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.*;

/**
 * Redis分布式锁功能
 */
@Service
public class RedisDisLockService {


    @Autowired
    StringRedisTemplate redisTemplate;



    //只要 RedisDisLockService 里面有容器性质的属性，有添操作就必有移除操作。否则，长时间容器撑爆。OOM
//    Person  person = new Person();
//    Map<Thread,String> map = new HashMap<>();
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    //如果使用期间不注意，很容易OOM。
    //原因是？大家都给里面放。因为RedisDisLockService永不销毁，导致 threadLocal 永不销毁。每一个人都往进放。OOM就是早晚的事情

    //延期线程结束定时任务就需要回收
    public void yanqi(ScheduledExecutorService scheduledThreadPool,String lockKey){
        /**
         * 周期执行
         * Runnable command,
         * long initialDelay,
         * long period,
         * TimeUnit unit
         */
        scheduledThreadPool.scheduleAtFixedRate(() -> {
            redisTemplate.expire(lockKey, 30L, TimeUnit.SECONDS);
        }, 10L, 10L, TimeUnit.SECONDS);


    }

    public boolean lock(String lockKey){

        String preToken = threadLocal.get();
        if(StringUtils.isEmpty(preToken)){
            //前面方法没有加过锁
            //原子加锁
            String token = UUID.randomUUID().toString();  //lock:52   lock:51
            Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 30L, TimeUnit.SECONDS);
            //开启续期机制
//            Thread thread = new Thread(() -> {
//                //每隔10s，续满期
//                ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
//                yanqi(scheduledThreadPool,lockKey);
//            });
//            thread.setDaemon(true);
//            thread.start();
            //为什么不能跟着主线程销毁？？？？？？
            threadLocal.set(token); //给当前线程中保存了一个数据
            return lock;
        }else {
            //前面方法加过锁了
            return true;
        }
        //setnx  lock  1； key：lock不存在才占坑
        //1、为了防止业务断电。导致解锁代码没执行，导致死锁。需要加锁的时候设置上锁的key的过期时间。让redis自动解锁
//        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "122aac");
//        Thread thread = Thread.currentThread();

        //加锁成功，if 还没判断，结果断电; 这是非原子操作
//        if(lock){
//            redisTemplate.expire("lock", Duration.ofSeconds(30)); //设置30s以后过期
//        }

        //给redis 发送的不是 setnx lock 1  而是 setnx lock 1  30s
        //原子加锁命令： set lock 1 EX 30 NX
//        return token;
    }

    public void unlock(String lockKey){
        //1、这种删除锁、如果业务超长，当前1号线程的锁（lock）已经过期了。redis自动删除了。2号获取到锁（lock）干活中...
        //2、1号线程终于干完了。执行删锁。就删除的是 2号线程的锁。

        //锁需要有一种机制，能识别当前锁是哪个人的锁。每个线程加锁的时候。给redis中保存的key：lock  value：随机值(跟当前线程绑定的)
        Thread thread = Thread.currentThread();
        //解锁。查出锁的当前值
        String token = threadLocal.get();
//        String lockValue = redisTemplate.opsForValue().get("lock"); //查redis。确实是1122.  但是返回数据的路上锁过期
//        if(lockValue.equals(token)){
//            //这是我当时加的锁。防止删掉别人的锁
//            redisTemplate.delete("lock");
//        }

        //必须用 Lua 脚本 ; 连判断带删除一体化
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        // if redis.call(get,"lock") == token then return redis.call(del,"lock") else return 0 end;
        /**
         * RedisScript<T> script: 脚本
         * List<K> keys, 操作的keys
         * Object... args
         */

        Long lock = redisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class), Arrays.asList(lockKey), token);
        System.out.println("解锁："+lock);
        threadLocal.remove();

    }
}
